/*
 SplatterAppDelegate.m
 
 The main application class that handles Bonjour networking
 as well as sets up the entire Splatter application.
 
 Created by David Kikuta.
 */

#import "SplatterAppDelegate.h"
#import "SplatterViewController.h"
#import "Picker.h"

#define kNumPads 3

// The Bonjour application protocol, which must:
// 1) be no longer than 14 characters
// 2) contain only lower-case letters, digits, and hyphens
// 3) begin and end with lower-case letter or digit
// It should also be descriptive and human-readable
// See the following for more information:
// http://developer.apple.com/networking/bonjour/faq.html
#define kAppIdentifier	@"splatter"

@interface SplatterAppDelegate ()
- (void) setup;
- (void) presentPicker:(NSString *)name;
@end


#pragma mark -

@implementation SplatterAppDelegate

@synthesize window;
@synthesize viewController;
@synthesize serverName;

- (void)showAlert:(NSString *)title {
	UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title message:@"Check your networking configuration." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
	[alertView show];
	[alertView release];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
    // Override point for customization after app launch. 
	[window addSubview:viewController.view];
	[window makeKeyAndVisible];
	
	[self setup];
	
	return YES;
}

- (void) dealloc {	
	[inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
	[inStream release];
	
	[outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
	[outStream release];
	
	[server release];
	
	[picker release];
	
	[window release];
	
	[super dealloc];
}

// Sets up a brand new TCPServer and begins advertising to other clients.
- (void) setup {
	[server release];
	server = nil;
	
	[inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
	[inStream release];
	inStream = nil;
	inReady = NO;
	
	[outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
	[outStream release];
	outStream = nil;
	outReady = NO;
	
	server = [TCPServer new];
	[server setDelegate:self];
	NSError *error = nil;
	if(server == nil || ![server start:&error]) {
		if (error == nil) {
			NSLog(@"Failed creating server: Server instance is nil");
		} else {
			NSLog(@"Failed creating server: %@", error);
		}
		[self showAlert:@"Failed creating server"];
		return;
	}
	
	// Start advertising to clients, passing nil for the name to tell Bonjour to use default name
	if(![server enableBonjourWithDomain:@"local" applicationProtocol:[TCPServer bonjourTypeFromIdentifier:kAppIdentifier] name:nil]) {
		[self showAlert:@"Failed advertising server"];
		return;
	}
	
	incomingDataBuffer = [[NSMutableData alloc] init];
}

// Creates the picker view that allows users to find other clients.
- (void) presentPicker:(NSString *)name {
	if (!picker) {
		picker = [[Picker alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] type:[TCPServer bonjourTypeFromIdentifier:kAppIdentifier]];
		picker.delegate = self;
	}
	
	picker.name = name;
	
	if (!picker.superview) {
	    [window addSubview:picker];
	}
}

- (void) destroyPicker {
	[picker removeFromSuperview];
	[picker release];
	picker = nil;
}

// If we display an error or an alert that the remote disconnected, handle dismissal and return to setup
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
	[self setup];
}

// Sends the drawing values over the network.
- (void) send:(NSDictionary *)dict {
	NSError *error = nil;
	NSData *data = [NSPropertyListSerialization dataWithPropertyList:dict format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
	NSMutableData *outgoingBuffer = [[NSMutableData alloc] init];
	int length = [data length];

	[outgoingBuffer appendBytes:&length length:sizeof(int)];
	[outgoingBuffer appendData:data];
	
	const uint8_t *buffer = [outgoingBuffer bytes];
	
	if (outStream && [outStream hasSpaceAvailable]) {
		if([outStream write:buffer maxLength:[outgoingBuffer length]] == -1) {
			[self showAlert:@"Failed sending data to peer"];
		}
	}	
}

- (void) openStreams {
	inStream.delegate = self;
	[inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
	[inStream open];
	outStream.delegate = self;
	[outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
	[outStream open];
}

- (void) disconnectStreams {
	if ([inStream streamStatus] != NSStreamStatusNotOpen && [outStream streamStatus] != NSStreamStatusNotOpen) {
	    [inStream close];
	    [outStream close];
		
		[self setup];
	}
}

- (void) browserViewController:(BrowserViewController *)bvc didResolveInstance:(NSNetService *)netService {
	if (!netService) {
		[self setup];
		return;
	}
	
	// note the following method returns inStream and outStream with a retain count that the caller must eventually release
	if (![netService getInputStream:&inStream outputStream:&outStream]) {
		[self showAlert:@"Failed connecting to server"];
		return;
	}
	
	[self openStreams];
}

@end


#pragma mark -
@implementation SplatterAppDelegate (NSStreamDelegate)

- (void) stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
	UIAlertView *alertView;
	switch(eventCode) {
		case NSStreamEventOpenCompleted: {
			[self destroyPicker];
			
			[server release];
			server = nil;
			
			if (stream == inStream)
				inReady = YES;
			else
				outReady = YES;
			
			if (inReady && outReady) {
				alertView = [[UIAlertView alloc] initWithTitle:@"Begin Drawing!" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil];
				[alertView show];
				[alertView release];
			}
			break;
		}
		case NSStreamEventHasBytesAvailable: {
			NSError *error;
			uint8_t buf[1024];
			unsigned int len = 0;
						
			if (stream == inStream) {		
				while ([inStream hasBytesAvailable]) {
					len = [(NSInputStream *)stream read:buf maxLength:1024];
					[incomingDataBuffer appendBytes:buf length:len];
				}
				if(len) {
					while(YES) {
					// Did we read the header yet?
					    if(packetBodySize == -1) {
					    // Do we have enough bytes in the buffer to read the header?
							if ([incomingDataBuffer length] >= sizeof(int)) {
								// extract length
								memcpy(&packetBodySize, [incomingDataBuffer bytes], sizeof(int));			

								// remove that chunk from buffer
								NSRange rangeToDelete = {0, sizeof(int)};
								[incomingDataBuffer replaceBytesInRange:rangeToDelete withBytes:NULL length:0];
						    }
						    else {
							    // We don't have enough yet. Will wait for more data.
							    break;
						    }
			     		}

						if([incomingDataBuffer length] >= packetBodySize) {
							NSData* raw = [NSData dataWithBytes:[incomingDataBuffer bytes] length:packetBodySize];
						    NSDictionary *dict = [NSPropertyListSerialization propertyListWithData:raw options:NSPropertyListImmutable format:NULL error:&error];
										
							[(SplatterViewController *)[self viewController] drawDot:[[dict valueForKey:@"width"] floatValue] 
																   withPreviousPoint:CGPointMake([[dict valueForKey:@"previousx"] floatValue] , [[dict valueForKey:@"previousy"] floatValue])
																	withCurrentPoint:CGPointMake([[dict valueForKey:@"currentx"] floatValue] , [[dict valueForKey:@"currenty"] floatValue])
																			  redVal:[[dict valueForKey:@"red"] floatValue]
																			greenVal:[[dict valueForKey:@"green"] floatValue]
																			 blueVal:[[dict valueForKey:@"blue"] floatValue]];
										
							// Remove that chunk from buffer
							NSRange rangeToDelete = {0, packetBodySize};
							[incomingDataBuffer replaceBytesInRange:rangeToDelete withBytes:NULL length:0];
										
							// We have processed the packet. Resetting the state.
							packetBodySize = -1;
						}
						else {
							// Not enough data yet. Will wait.
							break;
						}
					}
				}
				else {
					NSLog(@"No data.");
				}
			}
			break;
		}
		case NSStreamEventErrorOccurred: {
			NSLog(@"%s", _cmd);
			[self showAlert:@"Error encountered on stream!"];			
			break;
		}
			
		case NSStreamEventEndEncountered: {
			UIAlertView	*alertView;
			NSLog(@"%s", _cmd);
			
			alertView = [[UIAlertView alloc] initWithTitle:@"Peer Disconnected!" message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:@"Continue", nil];
			[alertView show];
			[alertView release];
			
			break;
		}
	}
}

@end


#pragma mark -

@implementation SplatterAppDelegate (TCPServerDelegate)

- (void) serverDidEnableBonjour:(TCPServer *)targetServer withName:(NSString *)string {
	NSLog(@"%s", _cmd);
	serverName = string;
}

- (void)didAcceptConnectionForServer:(TCPServer *)targetServer inputStream:(NSInputStream *)istr outputStream:(NSOutputStream *)ostr {
	if (inStream || outStream || targetServer != server)
		return;
	
	[server release];
	server = nil;
	
	inStream = istr;
	[inStream retain];
	outStream = ostr;
	[outStream retain];
	
	[self openStreams];
}

@end